package nio;

import org.junit.Test;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.Map;

/**
 * @author lzy
 * @version v1.0
 * Create DateTime: 2018/8/24 15:45
 * <p>
 * 一、通道（Channel）：用于源节点与目标节点的连接，在java nio中负责缓冲区中数据的传输。Channel本身不存储数据，因此需要配合缓冲区进行传输
 * <p>
 * 二、通道的主要实现类
 * java.nio.channels.Channel接口：
 * |-FileChannel (本地文件)
 * |-SocketChannel （tcp client）
 * |-ServerSocketChannel （tcp server）
 * |-DatagramChannel （udp）
 * <p>
 * 三、获取通道
 * 1.java针对支持通道的类提供了getChannel(）方法
 * 本地IO：
 * FileInputStream/FileOutputStream
 * RandomAccessFile
 * <p>
 * 网络IO:
 * Socket
 * ServerSocket
 * DatagramSocket
 * <p>
 * 2.在jdk1.7中的nio.2针对各个通道提供了一个静态方法open()
 * <p>
 * 3.在jdk1.7中的nio.2的Files工具类的newByteChannel()方法
 * <p>
 * 四、通道之间的数据传输
 * transferFrom()
 * transferTo()
 * <p>
 * 五、分散(Scatter)与聚集（Gather）
 * 分散读取（Scattering Reads）:将通道中的数据分散到多个缓冲区中
 * 聚集写入(Gathering Writes)：将多个缓冲区中的数据聚集到通道中
 * <p>
 * 六、字符集 CharSet
 * 编码：字符串->字节数组
 * 解码：字节数组->字符串
 */
public class TestChannel {

    @Test
    public void test6() throws CharacterCodingException {
        Charset cs1 = Charset.forName("GBK");
        //获取编码器
        CharsetEncoder encoder = cs1.newEncoder();
        //获取解码器
        CharsetDecoder decoder = cs1.newDecoder();

        CharBuffer buffer = CharBuffer.allocate(1024);
        buffer.put("测试语句");
        buffer.flip();

        //编码
        ByteBuffer bf = encoder.encode(buffer);
        for (int i = 0; i < bf.limit(); i++) {
            System.out.println(bf.get());
        }

        //解码
        bf.flip();
        CharBuffer charBuffer = decoder.decode(bf);
        System.out.println(charBuffer.toString());

        System.out.println("---------------");
        Charset cs2 = Charset.forName("utf-8");
        bf.rewind();
        CharBuffer cBuffer2 = cs2.decode(bf);
        System.out.println(cBuffer2.toString());

    }

    /**
     * 字符集
     */
    @Test
    public void test5() {
        Map<String, Charset> map = Charset.availableCharsets();
        map.forEach((key, value) -> System.out.println(key + ":" + value.toString()));
    }

    /**
     * 分散和聚集
     */
    @Test
    public void test4() {
        try {
            RandomAccessFile raf1 = new RandomAccessFile("D:\\system_path备份20180507.txt", "rw");
            // 1.获取通道
            FileChannel channel1 = raf1.getChannel();
            // 2.分配指定大小的缓冲区
            ByteBuffer buffer1 = ByteBuffer.allocate(100);
            ByteBuffer buffer2 = ByteBuffer.allocate(1024);

            // 3.分散读取
            ByteBuffer[] buffers = {buffer1, buffer2};
            channel1.read(buffers);

            for (ByteBuffer buffer : buffers) {
                buffer.flip();
            }

            System.out.println(new String(buffers[0].array(), 0, buffers[0].limit()));
            System.out.println("------------------------");
            System.out.println(new String(buffers[1].array(), 0, buffers[1].limit()));

            // 4.聚集写入
            RandomAccessFile raf2 = new RandomAccessFile("D:\\2.txt", "rw");
            FileChannel channel2 = raf2.getChannel();
            channel2.write(buffers);

            channel1.close();
            channel2.close();

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 通道之间的数据传输(直接缓冲区的方式)
     */
    @Test
    public void test3() {
        try (FileChannel inChannel = FileChannel.open(Paths.get("D:\\1.jpg"), StandardOpenOption.READ);
             FileChannel outChannel = FileChannel.open(Paths.get("D:\\2.jpg"), StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE)) {
//            inChannel.transferTo(0, inChannel.size(), outChannel);

            outChannel.transferFrom(inChannel, 0, inChannel.size());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 使用直接缓冲区完成文件的复制（内存映射文件）
     */
    @Test
    public void test2() {
        try (FileChannel inChannel = FileChannel.open(Paths.get("D:\\1.jpg"), StandardOpenOption.READ);
             FileChannel outChannel = FileChannel.open(Paths.get("D:\\2.jpg"), StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE)) {
            // 内存映射文件
            MappedByteBuffer inMapperBuf = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size());
            MappedByteBuffer outMapperBuf = outChannel.map(FileChannel.MapMode.READ_WRITE, 0, inChannel.size());

            //直接对缓冲区进行数据的读写操作
            byte[] dst = new byte[inMapperBuf.limit()];
            inMapperBuf.get(dst);
            outMapperBuf.put(dst);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 1.利用通道完成文件的复制(非直接缓冲区)
     */
    @Test
    public void test1() {
        try (
                FileInputStream fileInputStream = new FileInputStream("D:\\1.jpg");
                FileOutputStream fileOutputStream = new FileOutputStream("D:\\2.jpg");
                // 1.获取通道
                FileChannel inChannel = fileInputStream.getChannel();
                FileChannel outChannel = fileOutputStream.getChannel()) {

            // 2.分配一个指定大小的缓冲区
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            // 3.将通道中的数据写入缓冲区
            while (inChannel.read(buffer) != -1) {
                // 切换成读取数据的模式
                buffer.flip();
                // 4.将缓冲区中的数据写入到通道中
                outChannel.write(buffer);
                // 清空缓冲区
                buffer.clear();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
